from formencode import Schema
from formencode.variabledecode import variable_decode

from clutch.globals import C, G
from clutch import view
from clutch import exceptions as exc

class expose_api(object):
    def __call__(self, func):
        anno = Anno.get(func)
        anno.register_api()
        return func

class expose(object):
    def __init__(self, template='', content_type=None, exclude_names=None):
        if exclude_names is None: exclude_names = []
        engine = None
        if template=='json':
            engine, template = 'json', ''
        elif ':' in template:
            engine, template = template.split(':', 1)
        elif template:
            engine, template = (), template
        else:
            engine = template = None # plaintext
        if content_type is None:
            if engine == 'json':
                content_type = 'application/json'
            elif not template:
                content_type = 'text/plain'
            else:
                content_type = 'text/html'
        self.engine = engine
        self.template = template
        self.content_type = content_type
        self.exclude_names = exclude_names

    def __call__(self, func):
        anno = Anno.get(func)
        anno.register_renderer(
            self.content_type, self.engine, self.template, self.exclude_names)
        return func

class validate(object):

    def __init__(self, validators=None, **kw):
        self.error_handler = kw.pop('error_handler', None)
        if validators is None: validators = dict(kw)
        if isinstance(validators, dict):
            self.validator = Schema(**validators)
        else:
            self.validator = validators

    def __call__(self, func):
        anno = Anno.get(func)
        anno.validator = self.validator
        anno.error_handler = self.error_handler
        return func

class require(object):

    def __init__(self, *predicates):
        self.predicates = predicates

    def __call__(self, func):
        anno = Anno.get(func)
        anno.authorization += self.predicates
        return func

class Anno(object):

    def __init__(self, func):
        self.renderer = {}
        self.validator = None
        self.error_handler = None
        self.needs_txn = True
        self.authorization = []
        self.use_variabledecode = True
        self.exposed_api = False
        self.func = func

    def __repr__(self):
        l = [ 'Annotation for %s' % self.func,
              '    Renderers:' ]
        for k,v in self.renderer.items():
            l.append('        %s => %s' % (k, v))
        
        return '\n'.join(l)

    @property
    def content_types(self):
        return self.renderer.keys()

    @classmethod
    def get(klass, func):
        func = getattr(func, 'im_func', func)
        info = getattr(func, 'clutch_info', None)
        if info is None:
            info = func.clutch_info = klass(func)
        return info

    @property
    def exposed(self):
        return bool(self.renderer)

    def register_renderer(self, content_type, engine, template, exclude_names):
        self.renderer[content_type] = engine, template, exclude_names

    def register_api(self):
        self.exposed_api = True

    def get_renderer(self, accept):
        content_type = accept.best_match(self.renderer.keys())
        if content_type is None:
            engine = template = None
        else:
            engine, template, exclude_names = self.renderer[content_type]
        def null_renderer(template, value):
            if hasattr(value, '__iter__'):
                for s in value: yield s
            else:
                yield value
        if engine is None and template is None:
            return content_type, null_renderer, template
        engine = view.engines.get(engine, template)
        def renderer(template, value):
            value.update(C=C, G=G)
            for n in exclude_names:
                del value[n]
            return engine.render(value, template)
        if engine is None:
            return content_type, null_renderer, template
        else:
            return content_type, renderer, template

    def check_authorization(self):
        for a in self.authorization:
            if not a():
                if C.request.environ.get('REMOTE_USER') is None:
                    raise exc.HTTPUnauthorized()
                else:
                    raise exc.HTTPForbidden()

    def decode_params(self, params):
        if self.use_variabledecode:
            return variable_decode(params)
        return params

    def validate_params(self, params):
        if self.validator is None: return params
        return self.validator.to_python(params)
